/* chmod - Change file modes				Author: V. Archer */

/* Copyright 1991 by Vincent Archer
 *	You may freely redistribute this software, in source or binary
 *	form, provided that you do not alter this copyright mention in any
 *	way.
 */
/*
 * Copyright 2016 Liuxiaofeng for Junos.
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/unistd.h>
#include <sys/dirent.h>
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#define USR_MODES 	(S_ISUID|S_IRWXU)
#define GRP_MODES 	(S_ISGID|S_IRWXG)
#define EXE_MODES 	(S_IXUSR|S_IXGRP|S_IXOTH)
#define ALL_MODES 	(USR_MODES|GRP_MODES|S_IRWXO)

/* Common variables */
char *symbolic;
mode_t new_mode, u_mask;
int rflag, errors;
struct stat st;
char path[PATH_MAX + 1];

void usage()
{
	fprintf(stderr, "Usage: chmod [-R] mode file...\n");
	exit(1);
}

/* Parse a P1003.2 4.7.7-conformant symbolic mode. */
mode_t parsemode(const char *symbolic, mode_t oldmode)
{
	mode_t who, mask, newmode, tmpmask;
	char action;

	newmode = oldmode & ALL_MODES;
	while (*symbolic) {
		who = 0;
		for (; *symbolic; symbolic++) {
			if (*symbolic == 'a') {
				who |= ALL_MODES;
				continue;
			}
			if (*symbolic == 'u') {
				who |= USR_MODES;
				continue;
			}
			if (*symbolic == 'g') {
				who |= GRP_MODES;
				continue;
			}
			if (*symbolic == 'o') {
				who |= S_IRWXO;
				continue;
			}
			break;
		}
		if (!*symbolic || *symbolic == ',')
			usage();
		while (*symbolic) {
			if (*symbolic == ',')
				break;
			switch (*symbolic) {
			default:
				usage();
			case '+':
			case '-':
			case '=':
				action = *symbolic++;
			}
			mask = 0;
			for (; *symbolic; symbolic++) {
				if (*symbolic == 'u') {
					tmpmask = newmode & S_IRWXU;
					mask |= tmpmask | (tmpmask << 3)
							| (tmpmask << 6);
					symbolic++;
					break;
				}
				if (*symbolic == 'g') {
					tmpmask = newmode & S_IRWXG;
					mask |= tmpmask | (tmpmask >> 3)
							| (tmpmask << 3);
					symbolic++;
					break;
				}
				if (*symbolic == 'o') {
					tmpmask = newmode & S_IRWXO;
					mask |= tmpmask | (tmpmask >> 3)
							| (tmpmask >> 6);
					symbolic++;
					break;
				}
				if (*symbolic == 'r') {
					mask |= S_IRUSR | S_IRGRP | S_IROTH;
					continue;
				}
				if (*symbolic == 'w') {
					mask |= S_IWUSR | S_IWGRP | S_IWOTH;
					continue;
				}
				if (*symbolic == 'x') {
					mask |= EXE_MODES;
					continue;
				}
				if (*symbolic == 's') {
					mask |= S_ISUID | S_ISGID;
					continue;
				}
				if (*symbolic == 'X') {
					if (S_ISDIR(oldmode)
							|| (oldmode & EXE_MODES))
						mask |= EXE_MODES;
					continue;
				}
#ifdef S_ISVTX
				if (*symbolic == 't') {
					mask |= S_ISVTX;
					who |= S_ISVTX;
					continue;
				}
#endif
				break;
			}
			switch (action) {
			case '=':
				if (who)
					newmode &= ~who;
				else
					newmode = 0;
			case '+':
				if (who)
					newmode |= who & mask;
				else
					newmode |= mask & (~u_mask);
				break;
			case '-':
				if (who)
					newmode &= ~(who & mask);
				else
					newmode &= ~mask | u_mask;
			}
		}
		if (*symbolic)
			symbolic++;
	}
	return (newmode);
}

/* Apply a mode change to a given file system element. */
int do_change(char *name)
{
	mode_t m;
	DIR *dirp;
	struct dirent *entp;
	char *namp;

	if (stat(name, &st)) {
		perror(name);
		return (1);
	}
	if (S_ISLNK(st.st_mode) && rflag)
		return (0); /* Note: violates POSIX. */
	if (!symbolic)
		m = new_mode;
	else
		m = parsemode(symbolic, st.st_mode);
	if (chmod(name, m)) {
		perror(name);
		errors = 1;
	} else
		errors = 0;

	if (S_ISDIR(st.st_mode) && rflag) {
		if (!(dirp = opendir(name))) {
			perror(name);
			return (1);
		}
		if (name != path)
			strcpy(path, name);
		namp = path + strlen(path);
		*namp++ = '/';
		while ((entp = readdir(dirp)))
			if (entp->d_name[0] != '.'||
			   (entp->d_name[1] && (entp->d_name[1] != '.'
						|| entp->d_name[2]))) {
				strcpy(namp, entp->d_name);
				errors |= do_change(path);
			}
		closedir(dirp);
		*--namp = '\0';
	}
	return (errors);
}

/* Main module. The single option possible (-R) does not warrant a call to
 * the getopt() stuff.
 */
int main(int argc, char *argv[])
{
	int ex_code = 0;

	argc--;
	argv++;

	if (argc && strcmp(*argv, "-R") == 0) {
		argc--;
		argv++;
		rflag = 1;
	} else
		rflag = 0;

	if (!argc--)
		usage();
	if (!strcmp(argv[0], "--")) { /* Allow chmod -- -r, as in Draft11 example */
		if (!argc--)
			usage();
		argv++;
	}
	symbolic = *argv++;
	if (!argc)
		usage();

	if (*symbolic >= '0' && *symbolic <= '7') {
		new_mode = 0;
		while (*symbolic >= '0' && *symbolic <= '7')
			new_mode = (new_mode << 3) | (*symbolic++ & 07);
		if (*symbolic)
			usage();
		new_mode &= ALL_MODES;
		symbolic = NULL;
	} else
		u_mask = 0;

	while (argc--)
		if (do_change(*argv++))
			ex_code = 1;
	return (ex_code);
}
